<?php
/**
 * Rule Set
 * A set of rules to validate all at once and then attach messages to each
 * invalid field.
 * 
 * @package     Q-Validate
 * @subpackage  Rules
 * @author      Luke Visinoni <luke.visinoni@gmail.com>
 * @copyright   (c) 2010 Luke Visinoni <luke.visinoni@gmail.com>
 * @version     $Id: Set.php 37 2010-12-23 02:38:44Z luke.visinoni $
 */
class Q_Rule_Set implements Q_Rule_Interface {

    const ALL = '__ALL__';
    
    protected $_rules = array();
    
    protected $_msgs = array();
    
    /**
     * This is used for arrays
     */
    public function validate($data, $fieldsToValidate = null) {
    
        $this->_data = $data;
        if (!is_null($fieldsToValidate)) {
            $specfields = $fieldsToValidate;
        } elseif (is_string($fieldsToValidate)) {
            $specfields = array($fieldsToValidate);
        } else {
            $specfields = false;
        }
        // loop through each field in data and apply the "ALL" rules to it
        if (isset($this->_rules[self::ALL]) && is_array($this->_rules[self::ALL])) {
            foreach ($this->_data as $field => $value) {
                foreach ($this->_rules[self::ALL] as $rule) {
                    // if this field was not in $fieldsToValidate, don't validate it
                    if ($specfields && !in_array($field, $specfields)) continue;
                    // add the rule to the set for given field
                    $this->add($rule['rule'], $field, $rule['msg']);
                }
            }
        }
        // unset it now so loop doesn't try to use "ALL" rules
        unset($this->_rules[self::ALL]);
        // test each rule and add its messages to the message stack
        // loop through list of fields
        foreach ($this->_rules as $field => $rule) {
            // loop through each rule for each field
            foreach ($rule as $r) {
                // if the rule is a ruleset, it needs special attention (this is bad design - @todo - make set interface the same as rules)
                if ($r['rule'] instanceof Q_Rule_Set) {
                    // populate the ruleset with data and fields to validate
                    if (!$r['rule']->validate($data, $fieldsToValidate)) {
                        // include any messages from set into this set's messages
                        $messages = $r['rule']->getErrors();
                        // loop thtough the messages, adding where necessary
                        foreach ($messages as $field => $msg) {
                            $this->_msgs[$field] = $msg;
                        }
                    }
                } else {
                    // if this field was not in $fieldsToValidate, don't validate it
                    if ($specfields && !in_array($field, $specfields)) continue;
                    $r['rule']->setField($field);
                    if (!$r['rule']->validate($data)) {
                        if (!isset($this->_msgs[$field])) $this->_msgs[$field] = array();
                        // there is an issue here that causes messages to be created multiple times... weird
                        $msg = $r['rule']->doMessageReplacement($r['msg']);
                        if (!in_array($msg, $this->_msgs[$field])) $this->_msgs[$field][] = $msg;
                    }
                }
            }
        }
        return empty($this->_msgs);
    
    }
    
    /**
     * Adds a rule / ruleset to the ruleset
     */
    public function add(Q_Rule_Interface $rule, $fields = null, $messages = null) {
    
        if ($rule instanceof Q_Rule_Abstract) {
            $rule->setSet($this);
        }
        // if you don't specify a field, it applies it to all fields
        if (is_null($fields) || empty($fields)) {
            $fields = array(self::ALL);
        }
        // you can provide a string as $fields and it will apply to that field
        if (is_string($fields)) {
            $fields = array($fields);
        }
        // you can provide a string as message and it will be used for all fields
        if (!is_array($messages)) {
            $messages = array($messages);
        }
        // loop through each field and apply its rule
        foreach ($fields as $key => $field) {
            if (!isset($this->_rules[$field])) {
                $this->_rules[$field] = array();
            }
            // if there is a message specifically for this field, use it
            if (isset($messages[$key])) {
                $msg = $messages[$key];
            } else { 
                // otherwise use the last (or possibly only) one in the array
                $msg = end($messages);
            }
            $this->_rules[$field][] = array('rule' => $rule, 'msg' => $msg);
        }
        return $this;
    
    }
    
    public function getErrors() {
    
        return $this->_msgs;
    
    }

}
